查看原文
其他

孰优孰劣,gRPC和Rest性能大比拼

学研妹 Java学研大本营 2024-01-02

本文通过示例比较REST和gRPC性能。

长按关注《Java学研大本营》

我们在项目中使用Spring Boot和REST控制器。在本文中,我们将比较REST和gRPC,以探讨是否可以使用gRPC进行性能优化。

gRPC是一种RPC(远程过程调用)方法,通过使用Google Protobuf序列化基础架构,提供应用程序间的通信。

  • gRPC依赖于HTTP/2协议,而REST使用HTTP/1协议。
  • 在HTTP/1中,每个请求需要建立一个TCP连接,而在HTTP/2中可以重用同一个TCP连接。
  • 在HTTP/1中,头部以纯文本形式发送,而在HTTP/2中,头部和数据都被转换为二进制数据。
  • 在HTTP/2中,服务器可以对客户端请求发送多个响应消息。
  • 由于通信是通过二进制数据进行的,因此消息占用的空间更小,提供了高效的序列化和反序列化过程。

客户端和服务器应用程序可以使用不同的语言实现,并通过.proto文件进行通信。

这是在实现gRPC服务器-客户端应用程序时使用的示例proto定义。

syntax = "proto3";

package order.core;

// 生成的源代码选项
option java_package = "com.example.grpc.order";
option java_multiple_files = true;

message OrderRequest {
  int64 timestamp = 1;
  int32 count = 2;
  double amount = 3;
  string name = 4;
  bool active = 5;
  Type type = 6;
  InnerObject innerObject = 7;
  repeated string items = 8;
  reserved 9;
}
enum Type {
  UNKNOWN = 0;
  WEB = 1;
  LOCAL = 2;
}
message InnerObject {// Level 1
  Inner inner = 1;
}

message Inner {// Level 2
  int64 value = 1;
  bool  active = 2;
}

message OrderResponse {
  int64 timestamp = 1;
  string message = 2;
}

service OrderService {
  rpc order (OrderRequest) returns (OrderResponse);
}

1 支持的数据类型

下表显示了支持的数据类型及其对应的Java类型:

2 保留字段

在使用proto定义时,向后兼容性是一个重要的概念。如果你删除一个字段但没有指定先前使用的编号,其他用户可以使用相同的编号来表示不同的字段,这可能会导致严重的问题。为解决这个问题,你可以指定已删除字段的字段编号为保留字段。如果任何用户尝试使用这些字段标识符,协议缓冲区编译器将会报错。

message Foo {
  reserved 2159 to 11;
  reserved "foo""bar";
}

对于Java gRPC实现,编译器会为每个消息类型生成必要的类文件和构建器。

3 实现

为了进行比较,我这里实现了一个REST服务器-客户端和gRPC服务器-客户端。

4 REST服务器实现

创建一个Spring Boot启动器项目,并编写如下的REST控制器类。使用默认的Tomcat配置。

@RestController
@RequestMapping
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;

    @PostMapping(path = "/order")
    public OrderResponse order(@RequestBody OrderRequest request) {
        OrderDto orderDto = convertOrderDto(request);
        // 业务逻辑,创建订单
        return orderService.createOrder(orderDto);
    }
}

5 REST客户端实现

使用“Feign Client”实现REST客户端,代码如下:

@FeignClient(name = "orderService", url = "${integration.rest.url}")
public interface OrderFeignClient {

    @PostMapping(value = "/order")
    OrderResponse order(@RequestBody OrderRequest request);
}
@Service
@RequiredArgsConstructor
public class OrderRestClient {
    private final OrderFeignClient orderFeignClient;

    public OrderResponse createOrder(OrderRequest orderRequest) {
        OrderResponse response = orderFeignClient.order(orderRequest);
        return response;
    }
}

6 gRPC服务器实现

在服务器实现中,这里使用grpc-server-spring-boot-starter以及grpc-protobuf和grpc-stub依赖项。gRPC支持一元、客户端流、服务器流和双向流API类型。我实现了一个一元API,它从服务器发送请求并获取响应。

<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-server-spring-boot-starter</artifactId>
    <version>2.14.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-protobuf</artifactId>
    <version>1.51.0</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-stub</artifactId>
    <version>1.51.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
    <groupId>org.apache.tomcat</groupId>
    <artifactId>annotations-api</artifactId>
    <version>6.0.53</version>
    <scope>provided</scope>
</dependency>

在pom文件中添加以下构建步骤:

<build>
  <extensions>
      <extension>
          <groupId>kr.motd.maven</groupId>
          <artifactId>os-maven-plugin</artifactId>
          <version>1.6.2</version>
      </extension>
  </extensions>
  <plugins>
      <plugin>
          <groupId>org.xolstice.maven.plugins</groupId>
          <artifactId>protobuf-maven-plugin</artifactId>
          <version>0.6.1</version>
          <configuration>
              <protocArtifact>com.google.protobuf:protoc:3.19.2:exe:${os.detected.classifier}</protocArtifact>
              <pluginId>grpc-java</pluginId>
              <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.51.0:exe:${os.detected.classifier}</pluginArtifact>
          </configuration>
          <executions>
              <execution>
                  <goals>
                      <goal>compile</goal>
                      <goal>compile-custom</goal>
                  </goals>
              </execution>
          </executions>
      </plugin>
  </plugins>
</build>

编译器会为proto文件中的每个消息类型生成一个.java文件和一个相应的类。该插件将在项目的编译步骤中触发。

@GrpcService注解用于服务器实现。默认情况下,gRPC服务器将监听9090端口。端口设置可以通过应用程序.yaml文件中的grpc.server.port属性更改为其他值。

在业务层中,这里设计了gRPC服务器和REST服务器都调用同一个orderService。

@GrpcService
@RequiredArgsConstructor
public class OrderGrpcController extends OrderServiceGrpc.OrderServiceImplBase {
    private final OrderService orderService;

    @Override
    public void order(OrderRequest request, StreamObserver<OrderResponse> responseObserver) {
        OrderDto orderDto = convertOrderDto(request);
        // 业务逻辑,创建订单
        com.example.grpcserverapi.response.OrderResponse orderServiceResponse = orderService.createOrder(orderDto);
        
        OrderResponse response = OrderResponse.newBuilder()
                .setTimestamp(orderServiceResponse.getTimestamp())
                .setMessage(orderServiceResponse.getMessage())
                .build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

7 gRPC客户端实现

在客户端实现中,这里使用了grpc-client-spring-boot-starter以及grpc-protobuf和grpc-stub 依赖项。

<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-client-spring-boot-starter</artifactId>
  <version>2.14.0.RELEASE</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-protobuf</artifactId>
  <version>1.51.0</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-stub</artifactId>
  <version>1.51.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
  <groupId>org.apache.tomcat</groupId>
  <artifactId>annotations-api</artifactId>
  <version>6.0.53</version>
  <scope>provided</scope>
</dependency>

在应用程序.yaml文件中添加gRPC服务器的URL,如下所示:

grpc:
  client:
    orderService:
      address: static://本地主机:9090
      negotiation-type: plaintext

@GrpcClient(serverName)字段注解用于gRPC客户端存根实现。不要使用@Autowired注解进行客户端实现。

@Service
@RequiredArgsConstructor
public class OrderGrpcClient {

    @GrpcClient("orderService")
    private OrderServiceGrpc.OrderServiceBlockingStub blockingStub;

    public OrderResponse createOrder(OrderRequest request) {
        return blockingStub.order(request);
    }
}

当向REST客户端和gRPC客户端分别发送10,000个请求进行性能测试时,能得到以下结果:

gRPC平均每个请求的时间:0.3503毫秒

REST平均每个请求的时间:2.3109毫秒

请注意,这是在本地机器上进行性能测试的。

8 结论

根据性能结果,使用gRPC实现可以将延迟降低85%。

如果你希望服务之间通信更快,可以使用gRPC代替REST。由于通信是基于二进制数据进行的,如果服务频繁更改,维护兼容性会很困难。在这种情况下,REST可能更可取。

推荐书单

《Java Web从入门到精通(第3版)》

《Java Web从入门到精通(第3版)》从初学者角度出发,通过通俗易懂的语言、丰富多彩的实例,详细介绍了进行JavaWeb应用程序开发需要掌握的各方面技术。

本书共分21章,包括JavaWeb应用开发概述、HTML与CSS网页开发基础、JavaScript脚本语言、搭建开发环境、JSP基本语法、JSP内置对象、JavaBean技术、Servlet技术、过滤器和监听器、JavaWeb的数据库操作、EL(表达式语言)、JSTL标签、Ajax技术、Struts2基础、Struts2高级技术、Hibemate技术、Hibemate高级应用、Spring核心之IoC、Spring核心之AOP、SSM框架整合开发、九宫格记忆网等内容。

本书所有知识都结合具体实例进行介绍,涉及的程序代码给出了详细的注释,可以使读者轻松领会JavaWeb应用程序开发的精髓,快速提高开发技能。 另外,除了纸质内容之外,配套资源中还给出了海量开发资源库,主要内容如下:

  • 语音视频讲解:总时长19小时,共94段
  • 模块资源库:15个经典模块开发过程完整展现
  • 测试题库系统:596道能力测试题目
  • PPT电子教案
  • 实例资源库:1010个实例及源码详细分析
  • 项目案例资源库:15个企业项目开发过程完整展现
  • 面试资源库:369个企业面试真题

《Java Web从入门到精通(第3版)》可作为软件开发入门者的自学用书,也可作为高等院校相关专业的教学参考书,还可供开发人员查阅、参考。

购买链接:https://item.jd.com/13446953.html

精彩回顾

一文搞懂Java面向对象编程中的封装机制

一文搞懂Java面向对象编程中的继承机制

4步实现Android中的Websocket

用好这4个设计模式,完成Java中的大部分任务

5个Java开发者不可不知的编程库

长按关注《Java学研大本营》
长按访问【IT今日热榜】,发现每日技术热点
继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存